/**
 *    Copyright (C) 2008, 2009  Victor Smirnov <aist11@gmail.com>
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License along
 *    with this program; if not, write to the Free Software Foundation, Inc.,
 *    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *    See LICENSE.exceptions in the root folder of this project for possible
 *    exceptions
 *
 */

package ru.teleology.cubefs.editor.model;

import ru.teleology.cubefs.editor.hex.HexHelper;


public class ArrayTreeNode {
	
	private static final int CHILDREN_PER_NODE = 10;
	
	private final Provider provider;
	private long from;
	private final int level;
	
	public ArrayTreeNode(Provider provider) {
		this.provider = provider;
		from = 0;
		level = 0;
	}
	
	public ArrayTreeNode(int level, long from, ArrayTreeNode parent) {
		this.level = level;
		this.from = from;
		this.provider = parent.provider;
	}

	public Provider getProvider() {
		return provider;
	}

	public long getFrom() {
		return from;
	}

	public long getTo() {
		if (level == 0) {
			return provider.size();
		}
		else {
			long max = getMaxLeafCount();
			long size = provider.size();
			return from + max < size ? from + max : size; 
		}
	}

	public void setFrom(long from) {
		this.from = from;
	}

	public boolean isLeaf() {
		return getTo() - getFrom() <= CHILDREN_PER_NODE;
	}
	
	
	
	public int levels() {
		long size = provider.size();
		if (size == 0) {
			return 1;
		}
		else if (size <= CHILDREN_PER_NODE) {
			return 2;
		}
		else {
			int levels = 1;
			for (; size > 1; size = HexHelper.idiv(size, CHILDREN_PER_NODE), levels++);
			return levels;
		}
	}
	
	public long getMaxChildLeafCount() {
		return getMaxLeafCount(level + 1);
	}
	
	public long getMaxLeafCount() {
		return getMaxLeafCount(level);
	}
	
	public long getMaxLeafCount(int level) {
		int l0 = levels() - level - 1;
		long count = 1;
		for (int c = 0; c < l0; c++) count *= CHILDREN_PER_NODE;
		return count;
	}
	
	public Object getChild(int index) {
		if (isLeaf()) {
			return provider.leaf(index + getFrom());
		}
		else {
			long childCount = getMaxChildLeafCount();
			return new ArrayTreeNode(level + 1, from + index * childCount, this);
		}
	}
	
	public int getChildIndexForLeaf(long leafIndex) {
		long c0 = getMaxChildLeafCount();
		return (int) ((leafIndex - getFrom())/ c0);
	}
	
	public Object getChildForLeaf(long leafIndex) {
		return getChild(getChildIndexForLeaf(leafIndex));
	}
	
	public int getChildIndex(ArrayTreeNode child) {
		if (child.level - 1 == level) {
			return (int)((child.getFrom() -  from) / getMaxChildLeafCount()); 
		}
		else {
			throw new RuntimeException("Invalid child: "+child.level+" "+level);
		}
	}
	
	@Override
	public String toString() {
		return ""+from+"..."+(getTo()-1);
	}
	
	public long getNodesForLevel(int level) {
		long size = provider.size();
		for (int c = levels() - 1; c > level; c--) {
			if (size % CHILDREN_PER_NODE == 0) {
				size = size / CHILDREN_PER_NODE;
			}
			else {
				size = size / CHILDREN_PER_NODE + 1;
			}
		}
		return size;
	}
	
	
	public int getChildrenCount() {
		long levels = levels();
		if (levels == 1) {
			return 0;
		}
		else if (levels == 2){
			return (int) (getTo() - from);
		}
		else {
			long count = getMaxLeafCount();
			long size = provider.size();
			long maxNode = size / count + (size % count == 0 ? 0 : 1);  
			long nodeNum = from / count;
			
			if (nodeNum == maxNode - 1) {
				long c0 = getMaxChildLeafCount();
				long sz0 = size - from;
				return (int) (sz0 / c0 + (sz0 % c0 == 0 ? 0 : 1)); 				
			}
			else {
				return CHILDREN_PER_NODE;
			}
		}
	}
	
	public interface Provider {
		long size();
		Object leaf(long index);
		long getLeafIndex(Object leaf);
		Object getObject();
	}
}